iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0

https://ithelp.ithome.com.tw/upload/images/20230917/20119486GTzYiIKggY.png

前言

前一篇對於 Discord 已經具有一定基礎後,當然要來做一點小東西了,當然不會太難,大多都是前面所寫過跟使用過的東西哩。

專案準備

前面當然要準備一下專案,為了方便你練習,我也會盡可能先提供好相關的程式碼跟指令,所以你可以先輸入以下指令來建立專案:

mkdir example-discord-bot-crawler
cd example-discord-bot-crawler
npm init -y
touch index.js
touch register.js
touch .env

接下來就是安裝可能會需要使用的套件

npm i discord.js cheerio dotenv

實作

接下來前面基礎的程式碼我就不重複說明了,直接貼上來:

// index.js
require('dotenv').config(); // 引入 dotenv

const {
  Client,
  GatewayIntentBits,
  Partials,
  Events,
} = require('discord.js');

const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent,
  ],
  partials: [
    Partials.Channel,
  ],
});

client.once(Events.ClientReady, () => {
  console.log('Ready!');
});

client.login(process.env.DISCORD_TOKEN);
# .env
DISCORD_TOKEN=你的 Discord Token
DISCORD_CLIENT_ID=你的 Discord Client ID

我相信你看到我安裝 cheerio 就大概知道我們預期要做什麼了,沒錯,我們要來爬蟲了!

那麼我們這次需求要做什麼呢?

  • 爬取 IsRayNotArray 的文章標題與連結
  • 當使用者輸入 !blog 時,機器人會回傳一篇文章的標題與連結
  • 每天晚上九點自動發送一篇文章的標題與連結

爬取文章標題與連結

那麼爬蟲方面的話,其實也是前面寫過的範例程式碼

// crawler.js
const fs = require('fs');
const cheerio = require('cheerio');

const getData = async (url) => {
  try {
    const response = await fetch(url);
    const data = await response.text();
    return data;
  } catch (error) {
    console.log(error);
  }
}

const crawler = async () => {
  const target = 'https://israynotarray.com/'; // 目標網址
  const html = await getData(target);

  const $ = cheerio.load(html);

  const postTitleLink = $('.post-title-link');

  const data = [];

  postTitleLink.each((index, element) => {
    const title = $(element).text();
    const url = $(element).attr('href');
    data.push({
      title,
      url: `${target}${url}`, // 補上網域
    });
  });

  return data;
}

module.exports = crawler;

只是最後我們把它導出,這樣我們就可以在其他檔案使用了。

註冊指令

接下來為了實現可以輸入 !blog 時,機器人會回傳一篇文章的標題與連結,我們就需要註冊一個指令,這邊我們就使用前面所寫過的 register.js 來註冊指令:

// register.js
require('dotenv').config(); // 引入 dotenv

const {
  REST,
  Routes
} = require('discord.js');

const list = [
  {
    name: 'blog',
    description: '取得首頁第一頁隨機文章',
  },
];

const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);

const registerCommands = async (commands) => {
  try {
    console.log('Started refreshing application (/) commands.');

    await rest.put(Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), { body: commands });

    console.log('Successfully reloaded application (/) commands.');
  } catch (error) {
    console.error(error);
  }
}

registerCommands(list);

其實註冊指令也非常簡單,絕大部分都是不需要調整的,只需要調整 list 這個陣列就可以了,這邊我們只需要註冊一個指令,所以就只有一個物件,如果你要註冊多個指令,就可以在陣列中新增物件。

完成以上後,你可以先輸入 node register.js 來註冊指令,如果沒有錯誤的話,你就可以在 Discord 看到你剛剛註冊的指令了。

Note
查看方式很簡單,只需要在 Discord 中輸入 / 就可以看到你剛剛註冊的指令了;如果沒看到指令的話,你可以嘗試按下 Ctrl + R(Windows)或 Cmd + R(Mac)重新整理 Discord。

機器人

首先我們會需要使用一個事件,也就是 interactionCreate 事件,這個事件會在使用者與機器人互動時觸發,例如:使用者輸入指令、點擊按鈕等等。

// index.js
const {
  Client,
  GatewayIntentBits,
  Partials,
  Events,
} = require('discord.js');

// ... 略過其他程式碼

client.on(Events.InteractionCreate, async (interaction) => {
  console.log(interaction);
});

// ... 略過其他程式碼
client.login(process.env.DISCORD_TOKEN);

接下來請你先輸入 node index.js 啟動機器人,並試著在 Discord 上輸入 /blog,你會看到終端機上輸出了一個物件,這個物件就是 interaction,我們可以透過這個物件來判斷使用者輸入的指令是什麼

https://ithelp.ithome.com.tw/upload/images/20230917/20119486Vk1gQoT1hB.jpg

接下來我們必須判斷使用者輸入的指令是否為 /blog,如果是的話,我們就要回傳一篇文章的標題與連結,如果你不這樣做的話,當你有多個指令時,就會無法正確判斷使用者輸入的指令是什麼,而判斷方式很簡單,在 interaction 物件有一個 commandName 的屬性,這個屬性就是使用者輸入的指令名稱,所以我們只需要判斷這個屬性是否為 blog 就可以了

client.on(Events.InteractionCreate, async (interaction) => {
  if (interaction.commandName === 'blog') {
    // ...做一些事情
  }
});

接下來當然就是要引入我們的爬蟲程式碼了,這邊我們就直接引入 crawler.js,然後呼叫 crawler 函式,這個函式會回傳一個陣列,裡面包含了文章的標題與連結,我們只需要隨機取出一個就可以了,程式碼方面也不用擔心,我會補上註解說明:

// index.js
const crawler = require('./crawler'); // 引入爬蟲程式碼

// ... 略過其他程式碼

client.on(Events.InteractionCreate, async (interaction) => {
  // 判斷使用者輸入的指令是否為 /blog
  if (interaction.commandName === 'blog') {
    // 呼叫爬蟲程式碼,並取得資料
    const data = await crawler();
    // 依據資料長度隨機產生一個索引值
    const randomIndex = Math.floor(Math.random() * data.length);
    // 依照隨機產生的索引值取得資料
    const randomData = data[randomIndex];
    // 回傳資料給使用者
    await interaction.reply(`文章標題:${randomData.title}\n文章連結:${randomData.url}`);
  }
});

接下來我們就可以來啟動機器人了,請輸入 node index.js,然後在 Discord 上輸入 /blog,你就會看到機器人回傳了一篇文章的標題與連結了!而且也是隨機的!

https://ithelp.ithome.com.tw/upload/images/20230917/201194860FPEUfjxsJ.png

Note
當使用指令呼叫後,務必要使用 interaction.reply 來回傳資料給使用者,如果你使用 interaction.channel.send 的話,會發生錯誤,這是因為 interaction.channel.send 是用來回傳訊息的,而不是用來回傳指令的。

定時發送

接下來就是要來實作每天晚上九點自動發送一篇文章的標題與連結,這邊我們會使用到 cron 這個套件,這個套件可以讓我們定時執行程式碼,所以我們可以透過這個套件來實作。

首先我們先來安裝 cron

npm i cron

接著建立一個 postCron.js

touch postCron.js

並輸入以下程式碼

// postCron.js
const cron = require('cron');

const job = new cron.CronJob('0 21 * * *', () => {
  console.log('每天晚上九點執行一次');
});

job.start();

你可以先改成 * * * * *,這樣就會每分鐘執行一次,這樣你就可以先測試一下,看看有沒有正確執行了,若沒有問題的話,我們要將程式碼改成以下:

// postCron.js
const cron = require('cron');
const crawler = require('./crawler'); // 引入爬蟲程式碼

// 接收 client 作為參數
const postCron = (client) => {
  // 建立 job
  const job = new cron.CronJob('* * * * *', async () => {
    // 呼叫爬蟲程式碼,並取得資料
    const data = await crawler();
    // 依據資料長度隨機產生一個索引值
    const randomIndex = Math.floor(Math.random() * data.length);
    // 依照隨機產生的索引值取得資料
    const randomData = data[randomIndex];
    // 取得頻道
    const channel = client.channels.cache.get('頻道 ID'); // 你可以將頻道 ID 改成使用 process.env.DISCORD_CHANNEL_ID 來取得
    // 發送訊息
    await channel.send(`每日晚間九點隨機推薦 Ray 的一篇文章:[${randomData.title}](${randomData.url})`);
  });

  // 回傳 job
  return job;
}


module.exports = postCron;

Note
頻道 ID 的取得方式很簡單,只需要在 Discord 上右鍵點擊頻道,然後點擊「複製 ID」就可以了。

接著回到 index.js,引入 postCron.js,並在 client.once(Events.ClientReady, () => { ... }) 中啟動 job

// index.js
const postCron = require('./postCron'); // 引入 postCron

// ... 略過其他程式碼

client.once(Events.ClientReady, () => {
  console.log('Ready!');
  postCron(client).start();
});

接下來當你啟動機器人之後,每天晚上九點就會自動發送一篇文章的標題與連結了!

你可以看到我將 client 作為參數傳入 postCron,這樣的好處是,我們可以在 postCron 中使用 client,這樣就可以取得頻道,並發送訊息了。

Note
如果你想要在本機測試的話,請記得要把 0 21 * * * 改成 * * * * *,這樣就會每分鐘執行一次了;如果覺得 cron.js 中的程式碼跟 index.js 的程式碼過於雷同,你可以嘗試抽出來作為一個函式使用。

透過以上範例,你可以將你前面所學到的東西整合起來唷!那麼這一篇也差不多要告一個段落了,我們下一篇見。

Note
我先前也有分享過「用 OpenAI GPT-3 建立一個 Discord 聊天機器人」,若有興趣的話也可以參考唷。


上一篇
Day21 - Discord 基礎
下一篇
Day23 - 關於伺服器
系列文
《Node.js 不負責系列:把前端人員當作後端來用,就算是前端也能嘗試寫的後端~原來 Node.js 可以做這麼多事~》31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言